from wormbane.direction import *
from wormbane.divide_box import divide_box
from wormbane.Feature import *
from wormbane.geometry import *
from wormbane.Level import Level
from wormbane.Room import Room
from wormbane.shortest_path import shortest_path
import random


def random_pop(lst, random=random):
    index = random.randrange(len(lst))
    lst[index], lst[-1] = lst[-1], lst[index]
    return lst.pop()


def random_floor_tile(level, room, random=random):
    x = random.randrange(room.bounds.min.x + 1, room.bounds.max.x - 1)
    y = random.randrange(room.bounds.min.y + 1, room.bounds.max.y - 1)
    return level.tiles[x][y]


def generate_rooms(level, random=random):
    randint = random.randint
    min_size = 6
    max_size = 13
    min_dist = 4
    max_count = 7
    boxes = divide_box(level.bounds.min,
                       level.bounds.max + (min_dist, min_dist),
                       min_size + min_dist, max_count, randint)
    for min_p, max_p in boxes:
        max_x, max_y = max_p
        area = Box(min_p, (max_x - min_dist, max_y - min_dist))
        size = Vector(randint(min_size, min(area.size.x, max_size)),
                      randint(min_size, min(area.size.y, max_size)))
        pos = Vector(randint(area.min.x, area.max.x - size.x),
                     randint(area.min.y, area.max.y - size.y))
        yield Room(Box(pos, pos + size))


def generate_corridors(level, random=random):

    corners = set()
    room_walls = set()
    room_neighbors = set()
    for room in level.rooms:
        corners.update(room.corners)
        corners.add((room.min_x - 1, room.min_y - 1))
        corners.add((room.min_x - 1, room.max_y))
        corners.add((room.max_x, room.min_y - 1))
        corners.add((room.max_x, room.max_y))
        room_walls.update(room.walls)
        room_neighbors.update(room.neighbors)
    doors = set()
    corridor_neighbors = set()

    cardinal_offsets = [tuple(direction_to_planar_vector(dir))
                        for dir in ('north', 'west', 'east', 'south')]

    def cardinal_neighbors(tile):
        x, y = tile.pos
        tiles = level.tiles
        for dx, dy in cardinal_offsets:
            if (x + dx, y + dy) in level.bounds:
                yield tiles[x + dx][y + dy]

    def generate_corridor(room_a, room_b):
        start_pos = random_floor_tile(level, room_a, random).pos
        goal_pos = random_floor_tile(level, room_b, random).pos

        def goal(pos):
            return pos == goal_pos

        def neighbors(pos):
            x, y = pos
            tile = level.tiles[x][y]
            for neighbor in cardinal_neighbors(tile):
                if (neighbor.room in (None, room_a, room_b)
                    and neighbor.pos not in corners
                    and not (neighbor.pos in room_walls
                             and any(n.pos in doors
                                     for n in cardinal_neighbors(neighbor)))
                    and not (pos in room_walls
                             and neighbor.pos in room_walls)
                    and not (pos in room_neighbors
                             and neighbor.pos in room_neighbors)):
                    yield neighbor.pos

        def cost(a, b):
            return 1

        def estimate(pos):
            return manhattan_distance(pos, goal_pos)

        path = shortest_path(start_pos, neighbors, goal, 0, cost, estimate)
        new_corridors = []
        for x, y in path:
            tile = level.tiles[x][y]
            if type(tile.feature) is Wall:
                if tile.feature.dir:
                    if random.randrange(2):
                        tile.feature = Door(dir=tile.feature.dir)
                        tile.feature.closed = bool(random.randrange(2))
                        if tile.feature.closed and random.randrange(2):
                            tile.feature.locked = True
                    else:
                        if random.randrange(2):
                            tile.feature.secret = True
                        else:
                            tile.feature = Doorway(dir=tile.feature.dir)
                    doors.add(tile.pos)
                else:
                    if random.randrange(100) < 2:
                        tile.feature = Wall()
                        tile.feature.secret = True
                    else:
                        tile.feature = Corridor()
                    new_corridors.append(tile)
                    if tile.pos in corridor_neighbors:
                        break
        for tile in new_corridors:
            corridor_neighbors.update(n.pos for n in cardinal_neighbors(tile))

    # Track unconnected and connected rooms.
    unconnected = list(level.rooms)
    connected = list()

    # Choose a starting room.
    connected.append(random_pop(unconnected, random))

    # Connect the remaining rooms. It is important to generate the corridor
    # starting at the unconnected room, because corridor generation is halted
    # once the new corridor connects with an old.
    while unconnected:
        room = random_pop(unconnected, random)
        generate_corridor(room, random.choice(connected))
        connected.append(room)
        

def paint_room(room, level, lit):

    # boundaries
    north = room.bounds.max.y
    west = room.bounds.min.x
    east = room.bounds.max.x
    south = room.bounds.min.y

    # corners
    level.tiles[west][north - 1].feature = Wall('northwest')
    level.tiles[east - 1][north - 1].feature = Wall('northeast')
    level.tiles[west][south].feature = Wall('southwest')
    level.tiles[east - 1][south].feature = Wall('southeast')

    # walls
    for x in xrange(west + 1, east - 1):
        level.tiles[x][north - 1].feature = Wall('north')
    for y in xrange(south + 1, north - 1):
        level.tiles[west][y].feature = Wall('west')
    for y in xrange(south + 1, north - 1):
        level.tiles[east - 1][y].feature = Wall('east')
    for x in xrange(west + 1, east - 1):
        level.tiles[x][south].feature = Wall('south')

    # floor
    for x in xrange(west + 1, east - 1):
        for y in xrange(south + 1, north - 1):
            level.tiles[x][y].feature = Floor()

    # room and light
    for x in xrange(west, east):
        for y in xrange(south, north):
            level.tiles[x][y].room = room
            level.tiles[x][y].lit = lit


def generate_stairs(level, random=random):

    rooms = list(level.rooms)

    up_room = random_pop(rooms, random)
    up_tile = random_floor_tile(level, up_room, random)
    up_tile.feature = Stair(dir='up')
    level.stair_up_tile = up_tile

    down_room = random_pop(rooms, random)
    down_tile = random_floor_tile(level, down_room, random)
    down_tile.feature = Stair(dir='down')
    level.stair_down_tile = down_tile


def generate_level(random=random):
    level = Level()
    level.rooms = list(generate_rooms(level, random))
    for room in level.rooms:
        paint_room(room, level, bool(random.randrange(2)))
    generate_corridors(level, random)
    generate_stairs(level, random)
    return level


def test():
    generate_level()


if __name__ == '__main__':
    test()
